/*
 *  New WiFi - communicate with wpa_supplicant using the "new D-Bus api"
 *
 *  This file initially created by Google, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <net/ethernet.h>

#include <gdbus.h>

#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/assert.h>
#include <connman/plugin.h>
#include <connman/dbus.h>
#include <connman/device.h>
#include <connman/inet.h>
#include <connman/log.h>
#include <connman/notifier.h>
#include <connman/option.h>
#include <connman/profile.h>
#include <connman/service.h>
#include <connman/blob.h>
#include <connman/wifi.h>

#ifdef ENABLE_NSS
#include "nss.h"
#endif

#define	_DBG_WIFI(fmt, arg...)	DBG(DBG_WIFI, fmt, ## arg)

#define DBUS_TIMEOUT 5000		/* timeout waiting for a reply */

/*
 * The time(secs) for supplicant to complete a scan; this
 * is used to calculate the BSS expiration time in terms
 * of the long scan interval (kludge, need a better way).
 */
#define	SCAN_COMPLETION_BOUND	10

/*
 * Define scan interval while connected separately from
 * "idle" scan interval, in seconds.
 */
#define WIFI_BACKGROUND_SCAN_INTERVAL 3601

#define	IEEE80211_ELEMID_ERP		42
#define	IEEE80211_ELEMID_HTCAP		45
#define	IEEE80211_ELEMID_HTINFO		61

#define SUPPLICANT_NAME  "fi.w1.wpa_supplicant1"
#define SUPPLICANT_INTF  "fi.w1.wpa_supplicant1"
#define SUPPLICANT_PATH  "/fi/w1/wpa_supplicant1"

/* convenient shorthands */
#define	SUPPLICANT_INTERFACE_INTF	SUPPLICANT_INTF ".Interface"
#define	SUPPLICANT_NETWORK_INTF		SUPPLICANT_INTF ".Network"
#define	SUPPLICANT_BSS_INTF		SUPPLICANT_INTF ".BSS"

#define	DBUS_INTF	"org.freedesktop.DBus"

/* Taken from "WPA Supplicant - Common definitions" */
enum supplicant_state {
	/**
	 * WPA_DISCONNECTED - Disconnected state
	 *
	 * This state indicates that client is not associated, but is likely to
	 * start looking for an access point. This state is entered when a
	 * connection is lost.
	 */
	WPA_DISCONNECTED,

	/**
	 * WPA_INACTIVE - Inactive state (wpa_supplicant disabled)
	 *
	 * This state is entered if there are no enabled networks in the
	 * configuration. wpa_supplicant is not trying to associate with a new
	 * network and external interaction (e.g., ctrl_iface call to add or
	 * enable a network) is needed to start association.
	 */
	WPA_INACTIVE,

	/**
	 * WPA_SCANNING - Scanning for a network
	 *
	 * This state is entered when wpa_supplicant starts scanning for a
	 * network.
	 */
	WPA_SCANNING,

	/**
	 * WPA_AUTHENTICATING - Trying to authenticate with a BSS/SSID
	 *
	 * This state is entered when wpa_supplicant has found a suitable BSS
	 * to authenticate with and the driver is configured to try to
	 * authenticate with this BSS. This state is used only with drivers
	 * that use wpa_supplicant as the SME.
	 */
	WPA_AUTHENTICATING,

	/**
	 * WPA_ASSOCIATING - Trying to associate with a BSS/SSID
	 *
	 * This state is entered when wpa_supplicant has found a suitable BSS
	 * to associate with and the driver is configured to try to associate
	 * with this BSS in ap_scan=1 mode. When using ap_scan=2 mode, this
	 * state is entered when the driver is configured to try to associate
	 * with a network using the configured SSID and security policy.
	 */
	WPA_ASSOCIATING,

	/**
	 * WPA_ASSOCIATED - Association completed
	 *
	 * This state is entered when the driver reports that association has
	 * been successfully completed with an AP. If IEEE 802.1X is used
	 * (with or without WPA/WPA2), wpa_supplicant remains in this state
	 * until the IEEE 802.1X/EAPOL authentication has been completed.
	 */
	WPA_ASSOCIATED,

	/**
	 * WPA_4WAY_HANDSHAKE - WPA 4-Way Key Handshake in progress
	 *
	 * This state is entered when WPA/WPA2 4-Way Handshake is started. In
	 * case of WPA-PSK, this happens when receiving the first EAPOL-Key
	 * frame after association. In case of WPA-EAP, this state is entered
	 * when the IEEE 802.1X/EAPOL authentication has been completed.
	 */
	WPA_4WAY_HANDSHAKE,

	/**
	 * WPA_GROUP_HANDSHAKE - WPA Group Key Handshake in progress
	 *
	 * This state is entered when 4-Way Key Handshake has been completed
	 * (i.e., when the supplicant sends out message 4/4) and when Group
	 * Key rekeying is started by the AP (i.e., when supplicant receives
	 * message 1/2).
	 */
	WPA_GROUP_HANDSHAKE,

	/**
	 * WPA_COMPLETED - All authentication completed
	 *
	 * This state is entered when the full authentication process is
	 * completed. In case of WPA2, this happens when the 4-Way Handshake is
	 * successfully completed. With WPA, this state is entered after the
	 * Group Key Handshake; with IEEE 802.1X (non-WPA) connection is
	 * completed after dynamic keys are received (or if not used, after
	 * the EAP authentication has been completed). With static WEP keys and
	 * plaintext connections, this state is entered when an association
	 * has been completed.
	 *
	 * This state indicates that the supplicant has completed its
	 * processing for the association phase and that data connection is
	 * fully configured.
	 */
	WPA_COMPLETED,

	/**
	 * WPA_INVALID - Invalid state (parsing error)
	 *
	 * This state is returned if the string input is invalid. It is not
	 * an official wpa_supplicant state.
	 */
	WPA_INVALID,
};

static const char *supplicant_state_names[] = {
	[WPA_DISCONNECTED]	= "DISCONNECTED",
	[WPA_INACTIVE]		= "INACTIVE",
	[WPA_SCANNING]		= "SCANNING",
	[WPA_AUTHENTICATING]	= "AUTHENTICATING",
	[WPA_ASSOCIATING]	= "ASSOCIATING",
	[WPA_ASSOCIATED]	= "ASSOCIATED",
	[WPA_4WAY_HANDSHAKE]	= "4WAY_HANDSHAKE",
	[WPA_GROUP_HANDSHAKE]	= "GROUP_HANDSHAKE",
	[WPA_COMPLETED]		= "COMPLETED",
	[WPA_INVALID]		= "INVALID",
};

#define	SUPPLICANT_MAX_SSID_PER_SCAN	4

/*
 * Supplicant scan result request parameters.
 */
struct supplicant_scan_request {
	const char *scan_type;
	GSList *ssids;
};

#define	MAX_SSID_LEN	32		/* 802.11 SSID length (octets) */

/*
 * Supplicant network modes
 */
#define SUPPLICANT_NETWORK_MODE_MANAGED 0
#define SUPPLICANT_NETWORK_MODE_ADHOC 1
#define SUPPLICANT_NETWORK_MODE_HOSTAP 2
#define SUPPLICANT_NETWORK_MODE_DEFAULT SUPPLICANT_NETWORK_MODE_MANAGED

/*
 * Per-bss state collected from supplicant scan results.
 */
struct supplicant_result {
	char path[2*ETH_ALEN+2*MAX_SSID_LEN+1];
	char name[MAX_SSID_LEN+1];
	unsigned char addr[ETH_ALEN];
	unsigned char ssid[MAX_SSID_LEN];
	unsigned int ssid_len;
	gboolean privacy;
	gboolean has_wpa_psk;
	gboolean has_rsn_psk;
	gboolean has_802_1x;
	gboolean has_wps;
	dbus_int32_t frequency;
	dbus_int32_t signal;
	dbus_int32_t maxrate;

	unsigned char channel;
	unsigned short strength;
	enum connman_network_phymode phymode;
	const char *mode;
	const char *security;
};

/*
 * Per-interface "task state".
 */
struct supplicant_task {
	int ifindex;			/* ifnet index */
	char *ifname;			/* ifnet name */
	char *ifpath;			/* wpa_supplicant path for interface */
	struct connman_device *device;	/* device handle */
	struct connman_network *current_bss;/* current associated BSS (ref) */
	struct connman_network *network;/* current connection request (ref) */
					/* pending connection request (ref) */
	struct connman_network *pending_network;
	enum supplicant_state state;	/* wpa_supplicant state from status */
	gboolean scanning;		/* TRUE if scanning in progress */
	gboolean flush_pending;		/* TRUE if flush BSS needed */
	time_t resume_time;		/* time of last system resume */
	int scangen;			/* scan generation number */
	DBusPendingCall *scan_call;	/* async dbus call to request scan */
	GHashTable *netblocks;		/* supplicant network block handles */
	GHashTable *bss;		/* supplicant BSS handles */
	int (*next_method)(struct supplicant_task *);
	char *country;			/* current installed country code */
};

static GSList *task_list = NULL;

static DBusConnection *connection;

static int interface_set_ap_scan(struct supplicant_task *);
static int interface_set_fast_reauth(struct supplicant_task *);
static int interface_set_country(struct supplicant_task *, const char *);
static int interface_set_bss_expire_age(struct supplicant_task *, uint32_t);
static char *network_add(struct supplicant_task *,
	struct connman_network *network);
static int network_remove(struct supplicant_task *, const void *handle);
static int interface_remove_all(struct supplicant_task *);
static int interface_flush_bss(struct supplicant_task *, int age);
static int task_connect(struct supplicant_task *task,
	struct connman_network *network);

static void free_task(struct supplicant_task *task)
{
	_DBG_WIFI("task %p", task);

	connman_device_unref(task->device);

	if (task->current_bss != NULL)
		connman_network_unref(task->current_bss);
	if (task->network != NULL)
		connman_network_unref(task->network);
	if (task->pending_network != NULL)
		connman_network_unref(task->pending_network);
	g_hash_table_destroy(task->netblocks);
	g_hash_table_destroy(task->bss);
	g_free(task->ifname);
	g_free(task->ifpath);
	g_free(task->country);
	g_free(task);
}

static struct supplicant_task *find_task_by_index(int index)
{
	GSList *list;

	for (list = task_list; list; list = list->next) {
		struct supplicant_task *task = list->data;

		if (task->ifindex == index)
			return task;
	}

	return NULL;
}

static struct supplicant_task *find_task_by_path(const char *path)
{
	GSList *list;

	for (list = task_list; list; list = list->next) {
		struct supplicant_task *task = list->data;

		/* NB: prefix handles BSS paths */
		if (g_str_has_prefix(path, task->ifpath) == TRUE)
			return task;
	}

	return NULL;
}

static void task_error(struct supplicant_task *task, const char *fmt, ...)
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	snprintf(buf, sizeof(buf), "%s: %s", task->ifname, fmt);
	connman_verror(buf, ap);
	va_end(ap);
}

static void task_info(struct supplicant_task *task, const char *fmt, ...)
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	snprintf(buf, sizeof(buf), "%s: %s", task->ifname, fmt);
	connman_vinfo(buf, ap);
	va_end(ap);
}

/*
 * D-Bus helper functions.
 */

static const char *__dbus_obj_type_name(int type)
{
	static char hex[16];

	switch (type) {
	case DBUS_TYPE_BOOLEAN:		return "boolean";
	case DBUS_TYPE_INT16:		return "int16";
	case DBUS_TYPE_UINT16:		return "uint16";
	case DBUS_TYPE_UINT32:		return "uint32";
	case DBUS_TYPE_INT32:		return "int32";
	case DBUS_TYPE_STRING:		return "string";
	case DBUS_TYPE_DICT_ENTRY:	return "dict";
	case DBUS_TYPE_ARRAY:		return "array";
	case DBUS_TYPE_OBJECT_PATH:	return "obj path";
	}
	snprintf(hex, sizeof(hex), "type 0x%x", type);
	return hex;
}
static gboolean __check_iter_arg_type(struct supplicant_task *task,
	DBusMessageIter *iter, int type, const char *func)
{
	if (dbus_message_iter_get_arg_type(iter) != type) {
		task_error(task, "%s: expected %s got %s", func,
		    __dbus_obj_type_name(type),
		    __dbus_obj_type_name(dbus_message_iter_get_arg_type(iter)));
		return FALSE;
	}
	return TRUE;
}
#define	DBUS_EXPECT_ITER_ARG(iter, type, bad) do {			\
	if (__check_iter_arg_type(task, iter, type, __func__) == FALSE)	{ \
		bad;							\
	}								\
} while (0)

/*
 * Send a method call and arrange for a reply callback.
 */
static int send_call2(struct supplicant_task *task, DBusMessage *message,
	void (*reply_notify)(DBusPendingCall *call, void *user_data),
	DBusPendingCall **pcall, const char *func, const char *calldesc)
{

	if (dbus_connection_send_with_reply(connection, message, pcall,
	    DBUS_TIMEOUT) == FALSE) {
		task_error(task, "%s: failed to %s", func, calldesc);
		dbus_message_unref(message);
		return -EIO;
	}
	if (*pcall == NULL) {
		task_error(task, "%s: D-Bus connection not available", func);
		dbus_message_unref(message);
		return -EIO;
	}
	dbus_pending_call_set_notify(*pcall, reply_notify, task, NULL);
	dbus_message_unref(message);

	return -EINPROGRESS;
}

/*
 * Send a method call and arrange for a reply callback.
 */
static int send_call(struct supplicant_task *task, DBusMessage *message,
	void (*reply_notify)(DBusPendingCall *call, void *user_data),
	const char *func, const char *calldesc)
{
	DBusPendingCall *call;
	return send_call2(task, message, reply_notify, &call, func, calldesc);
}

/*
 * Send a method call and block waiting for reply.
 */
static DBusMessage *send_call_and_block(struct supplicant_task *task,
	DBusMessage *message, const char *func, const char *request)
{
	DBusMessage *reply;
	DBusError error;

	dbus_error_init(&error);
	reply = dbus_connection_send_with_reply_and_block(connection,
	    message, -1, &error);
	if (reply == NULL) {
		if (dbus_error_is_set(&error) == TRUE) {
			task_error(task, "%s: %s", func, error.message);
			dbus_error_free(&error);
		} else
			task_error(task, "%s: %s failed", func, request);
	}
	dbus_message_unref(message);

	return reply;
}

static const char *get_objpath(struct supplicant_task *task, DBusMessage *msg,
	const char *func, const char *msgdesc)
{
	DBusError error;
	const char *path;

	dbus_error_init(&error);
	if (dbus_message_get_args(msg, &error, DBUS_TYPE_OBJECT_PATH, &path,
						DBUS_TYPE_INVALID) == FALSE) {
		if (dbus_error_is_set(&error) == TRUE) {
			task_error(task, "%s: %s", func, error.message);
			dbus_error_free(&error);
		} else
			task_error(task, "%s: wrong arguments for %s", func,
			    msgdesc);
		path = NULL;		/* NULL indicates failure */
	}
	dbus_message_unref(msg);

	return path;
}

/*
 * Handle GetInterface and CreateInterface method call replies.
 */
static void interface_reply(DBusPendingCall *call, void *user_data)
{
	struct supplicant_task *task = user_data;
	DBusMessage *reply;
	const char *path;

	_DBG_WIFI("task %p", task);

	reply = dbus_pending_call_steal_reply(call);
	dbus_pending_call_unref(call);
	if (reply == NULL)
		goto failed;

	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
		if (task->next_method != NULL) {
			int err = task->next_method(task);
			if (err < 0 && err != -EINPROGRESS) {
				dbus_message_unref(reply);
				goto failed;
			}
		}
		dbus_message_unref(reply);
		return;
	}

	path = get_objpath(task, reply, __func__, "Get/CreateInterface");
	if (path == NULL)
		goto failed;

	_DBG_WIFI("path %s", path);
	task->ifpath = g_strdup(path);
	task_list = g_slist_append(task_list, task);

	interface_remove_all(task);
	interface_set_ap_scan(task);
	interface_set_fast_reauth(task);
	interface_set_country(task, connman_profile_get_country());
	interface_set_bss_expire_age(task,
	    connman_device_bgscan_get_long(task->device)+SCAN_COMPLETION_BOUND);
	interface_flush_bss(task, 0);
	connman_device_set_powered(task->device, TRUE);
	return;
failed:
	/* NB: critical error, be sure something is logged */
	task_error(task, "%s: Get/CreateInterface failed");
	free_task(task);
}

/*
 * Get a handle on the interface.
 */
static int interface_get(struct supplicant_task *task)
{
	DBusMessage *message;

	_DBG_WIFI("task %p", task);

	message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH,
					SUPPLICANT_INTF, "GetInterface");
	if (message == NULL)
		return -ENOMEM;

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_append_args(message, DBUS_TYPE_STRING, &task->ifname,
	    DBUS_TYPE_INVALID);

	task->next_method = NULL;

	return send_call(task, message, interface_reply, __func__,
	    "get interface");
}

/*
 * Create the interface using the specified driver and config
 * file.  The latter is used to convey parameters that we cannot
 * otherwise specify through the dbus api.  If the interface
 * cannot be created, fallback to doing a get.
 */
static int interface_create(struct supplicant_task *task)
{
	const char *driver = connman_option_get_string("wifi");
	const char *conffile = SCRIPTDIR "/wpa_supplicant.conf";
	DBusMessage *message;
	DBusMessageIter array, dict;

	_DBG_WIFI("task %p", task);

	message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH,
					SUPPLICANT_INTF, "CreateInterface");
	if (message == NULL)
		return -ENOMEM;

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_iter_init_append(message, &array);
	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
	    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
	        DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
	    DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	connman_dbus_dict_append_variant(&dict, "Ifname",
	    DBUS_TYPE_STRING, &task->ifname);
	connman_dbus_dict_append_variant(&dict, "Driver",
	    DBUS_TYPE_STRING, &driver);
	/*
	 * TODO(sleffler) set global properties directly once wpa_supplicant
	 * supports this.
	 */
	connman_dbus_dict_append_variant(&dict, "ConfigFile",
	    DBUS_TYPE_STRING, &conffile);
	dbus_message_iter_close_container(&array, &dict);

	/* NB: if we get an error response, try to get the interface */
	task->next_method = interface_get;

	return send_call(task, message, interface_reply, __func__,
	    "create interface");
}

static void interface_remove_reply(DBusPendingCall *call, void *user_data)
{
	struct supplicant_task *task = user_data;
	DBusMessage *reply;

	_DBG_WIFI("task %p", task);

	reply = dbus_pending_call_steal_reply(call);
	dbus_pending_call_unref(call);
	connman_device_set_powered(task->device, FALSE);
	connman_inet_ifdown(task->ifindex);
	free_task(task);
	dbus_message_unref(reply);
}

/*
 * Remove an interface in the supplicant.
 */
static int interface_remove(struct supplicant_task *task)
{
	DBusMessage *message;

	_DBG_WIFI("task %p", task);

	message = dbus_message_new_method_call(SUPPLICANT_NAME, SUPPLICANT_PATH,
					SUPPLICANT_INTF, "RemoveInterface");
	if (message == NULL)
		return -ENOMEM;

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &task->ifpath,
	    DBUS_TYPE_INVALID);

	return send_call(task, message, interface_remove_reply, __func__,
	    "remove interface");
}

/*
 * Send the supplicant a .Interface request over the d-bus.
 * The handle is supplied as the object path for the request
 * if it is not NULL.
 */
static DBusMessage *interface_send_request(struct supplicant_task *task,
	const char *request, const void *handle)
{
	DBusMessage *message;

	message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath,
				SUPPLICANT_INTERFACE_INTF, request);
	if (message == NULL) {
		task_error(task, "%s: cannot allocate dbus msg", request);
		return NULL;
	}
	dbus_message_set_auto_start(message, FALSE);
	if (handle != NULL) {
		_DBG_WIFI("Interface.%s handle %s", request,
		    (const char *) handle);
		dbus_message_append_args(message,
		    DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID);
	} else {
		/* Disconnect supplies no arg/handle */
		_DBG_WIFI("Interface.%s", request);
	}
	return send_call_and_block(task, message, __func__, request);
}

static int interface_set_property(struct supplicant_task *task,
	const char *prop, int type, void *val)
{
	DBusMessage *message, *reply;
	const char *interface = SUPPLICANT_INTERFACE_INTF;
	DBusMessageIter iter;

	_DBG_WIFI("task %p path %s prop %s", task, task->ifpath, prop);

	message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath,
				DBUS_INTF ".Properties", "Set");
	if (message == NULL)
		return -ENOMEM;

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_iter_init_append(message, &iter);
	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface);
	connman_dbus_property_append_variant(&iter, prop, type, val);

	reply = send_call_and_block(task, message, __func__, prop);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

static int interface_set_ap_scan(struct supplicant_task *task)
{
	int ap_scan = 1;
	return interface_set_property(task, "ApScan",
	    DBUS_TYPE_UINT32, &ap_scan);
}

static int interface_set_fast_reauth(struct supplicant_task *task)
{
	dbus_bool_t fast_reauth = FALSE;
	return interface_set_property(task, "FastReauth",
	    DBUS_TYPE_BOOLEAN, &fast_reauth);
}

static int interface_set_country(struct supplicant_task *task,
    const char *country)
{
	if (g_strcmp0(country, task->country) == 0)
		return 0;
	g_free(task->country);
	task->country = g_strdup(country);
	return interface_set_property(task, "Country",
	    DBUS_TYPE_STRING, &task->country);
}

static int interface_set_bss_expire_age(struct supplicant_task *task,
    uint32_t expire_age)
{
	return interface_set_property(task, "BSSExpireAge",
	    DBUS_TYPE_UINT32, &expire_age);
}

static void add_blob(DBusMessageIter *dict, void *arg)
{
	const struct blob *blob = arg;

	dbus_message_iter_append_fixed_array(dict, DBUS_TYPE_BYTE,
	    blob->data, blob->len);
}

static void append_byte_array(DBusMessageIter *dict, const char *key,
    const void *data, int len)
{
	/* NB: &data is required by connman_dbus_dict_append_variant_array */
	struct blob blob = { .data = (void *)&data, .len = len };
	connman_dbus_dict_append_variant_array(dict, key, DBUS_TYPE_BYTE,
	    add_blob, &blob);
}

static void append_bgscan(DBusMessageIter *dict,
	struct connman_network *network)
{
	struct connman_device *device = connman_network_get_device(network);
	char bgscan_buf[80];
	const char *bgscan = bgscan_buf;
	const char *method;
	int long_interval = WIFI_BACKGROUND_SCAN_INTERVAL;

	method = connman_device_bgscan_get_method(device);

	if (method == NULL) {
		/*
		 * If multiple APs are detected for this SSID, configure
		 * the "simple" method by default.  Otherwise, disable
		 * background scanning completely.
		 */
		if (connman_network_get_peer_count(network) > 0)
			method = "simple";
		else {
			connman_info("%s: Disabling background scan since "
				     "no peers to this network were found.",
				     __func__);
			return;
		}
	} else {
		/*
		 * If the background scan method was explicitly specified,
		 * honor the configured background scan interval.
		 */
		long_interval = connman_device_bgscan_get_long(device);
	}

	connman_info("%s: Using %s method for background scan with "
		     "long interval %d", __func__, method,
		     long_interval);
	if (g_strcmp0(method, "simple") == 0 ||
	    g_strcmp0(method, "learn") == 0) {

		snprintf(bgscan_buf, sizeof(bgscan_buf), "%s:%d:%d:%d",
		    method,
		    connman_device_bgscan_get_short(device),
		    connman_device_bgscan_get_signal_threshold(device),
		    long_interval);

		connman_dbus_dict_append_variant(dict, "bgscan",
		    DBUS_TYPE_STRING, &bgscan);
	}
}

static int check_8021x(struct connman_network *network)
{
	const char *identity =
	    connman_network_get_string(network, CONNMAN_WIFI_EAP_IDENTITY);

	if (identity == NULL || strlen(identity) == 0)
		return -EINVAL;

	return 0;
}

static const char *append_from_network(DBusMessageIter *dict,
				       struct connman_network *network,
				       const char *network_key,
				       const char *supplicant_key)
{
	const char *value;

	value = connman_network_get_string(network, network_key);
	if (value != NULL)
		connman_dbus_dict_append_variant(dict, supplicant_key,
                        DBUS_TYPE_STRING, &value);

	return value;
}


static void append_8021x(DBusMessageIter *dict,
	struct connman_network *network)
{
	const char *engine_id = "pkcs11";
	dbus_int32_t engine = 1;
	gboolean use_pkcs11 = FALSE;
	const char *value;
#ifdef SSL_ROOTS
	const char *ca_path = SSL_ROOTS;
	gboolean use_system_cas;

	use_system_cas = connman_network_get_uint8(network,
		CONNMAN_WIFI_EAP_USESYSTEMCAS);

	if (use_system_cas == TRUE)
		connman_dbus_dict_append_variant(dict, "ca_path",
			DBUS_TYPE_STRING, &ca_path);
        else
#endif
        {
                if (connman_network_get_string(network,
                                        CONNMAN_WIFI_EAP_CACERT) == NULL &&
                        connman_network_get_string(network,
                                CONNMAN_WIFI_EAP_CACERTID) == NULL)
                        connman_warn("Network \"%s\":"
                                " No certificate authorities are configured."
                                " Server certificates will be accepted"
                                " unconditionally.",
                                connman_network_get_string(network, "Name"));
        }
	if (append_from_network(dict, network, CONNMAN_WIFI_EAP_KEY_MGMT,
	    "key_mgmt") == NULL) {
		/* NB: default to WPA-EAP if not specified */
		const char *key_mgmt = "WPA-EAP";
		connman_dbus_dict_append_variant(dict, "key_mgmt",
		    DBUS_TYPE_STRING, &key_mgmt);
	}
	append_from_network(dict, network, CONNMAN_WIFI_EAP_IDENTITY,
                "identity");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_EAP, "eap");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_INNEREAP, "phase2");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY,
                "anonymous_identity");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_CLIENTCERT,
                "client_cert");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_PRIVATEKEY,
                "private_key");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD,
                "private_key_passwd");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_CACERT, "ca_cert");
	append_from_network(dict, network, CONNMAN_WIFI_EAP_PASSWORD,
                "password");
	value = append_from_network(dict, network, CONNMAN_WIFI_EAP_CERTID,
                "cert_id");
	if (value != NULL)
		use_pkcs11 = TRUE;
	value = append_from_network(dict, network, CONNMAN_WIFI_EAP_KEYID,
                "key_id");
	if (value != NULL)
		use_pkcs11 = TRUE;
	value = append_from_network(dict, network, CONNMAN_WIFI_EAP_CACERTID,
                "ca_cert_id");
	if (value != NULL)
		use_pkcs11 = TRUE;

	if (use_pkcs11) {
		append_from_network(dict, network, CONNMAN_WIFI_EAP_PIN, "pin");
		connman_dbus_dict_append_variant(dict, "engine",
		    DBUS_TYPE_INT32, &engine);
		connman_dbus_dict_append_variant(dict, "engine_id",
		    DBUS_TYPE_STRING, &engine_id);
	}

#ifdef ENABLE_NSS
        value = connman_network_get_string(network,
                                           CONNMAN_WIFI_EAP_CACERTNSS);
        if (value != NULL) {
                char *filename;
                const uint8_t *ssid;
                unsigned int ssid_len;

                ssid = connman_network_get_blob(network, "WiFi.SSID",
                        &ssid_len);

                filename = nss_get_der_certfile(value, ssid, ssid_len);
                if (filename != NULL) {
                        connman_dbus_dict_append_variant(dict, "ca_cert",
                                DBUS_TYPE_STRING, &filename);
                }
                g_free(filename);
        }
#endif

}

static int check_psk(struct connman_network *network)
{
	const char *passphrase =
	    connman_network_get_string(network, "WiFi.Passphrase");
	return (passphrase == NULL || strlen(passphrase) == 0 ? -EINVAL : 0);
}

static void append_psk(DBusMessageIter *dict,
	struct connman_network *network, const char *proto)
{
	const char *key_mgmt = "WPA-PSK";
	const char *passphrase;

	connman_dbus_dict_append_variant(dict, "key_mgmt",
	    DBUS_TYPE_STRING, &key_mgmt);
	connman_dbus_dict_append_variant(dict, "proto",
	    DBUS_TYPE_STRING, &proto);

	passphrase = connman_network_get_string(network, "WiFi.Passphrase");
	connman_dbus_dict_append_variant(dict, "psk",
	    DBUS_TYPE_STRING, &passphrase);
}

static int check_wep(struct connman_network *network)
{
	const char *passphrase =
	    connman_network_get_string(network, "WiFi.Passphrase");
	return (passphrase == NULL || strlen(passphrase) == 0 ? -EINVAL : 0);
}

static void append_wep(DBusMessageIter *dict,
	struct connman_network *network)
{
	const char *key_mgmt = "NONE";
	/*
	 * NB: mac80211-capable devices can auto-select
	 * between open and shared key auth.
	 */
	const char *auth_alg = "OPEN SHARED";
	char key_name[12];		/* NB: "wep_keyN" */
	dbus_uint32_t key_index;
	unsigned int key_len;
	const void *key_matter;

	connman_dbus_dict_append_variant(dict, "auth_alg",
	    DBUS_TYPE_STRING, &auth_alg);

	connman_dbus_dict_append_variant(dict, "key_mgmt",
	    DBUS_TYPE_STRING, &key_mgmt);

	key_index = connman_network_get_uint8(network, "WiFi.WEPKeyIndex");
	snprintf(key_name, sizeof(key_name), "wep_key%d", key_index);

	key_matter = connman_network_get_blob(network, "WiFi.WEPKey", &key_len);
	CONNMAN_ASSERT(key_matter != NULL);
	append_byte_array(dict, key_name, key_matter, key_len);

	connman_dbus_dict_append_variant(dict, "wep_tx_keyidx",
	    DBUS_TYPE_UINT32, &key_index);
}

static void append_mode(DBusMessageIter *dict, struct connman_network *network)
{
	dbus_int32_t mode_val = SUPPLICANT_NETWORK_MODE_DEFAULT;
	const char *mode_str = connman_network_get_string(network, "WiFi.Mode");

	if (g_ascii_strcasecmp(mode_str, "managed") == 0)
		mode_val = SUPPLICANT_NETWORK_MODE_MANAGED;
	else if (g_ascii_strcasecmp(mode_str, "adhoc") == 0) {
		/*
		 * NB: wpa_supplicant does not use scan results for
		 * configuring IBSS so we need to manually select the
		 * frequency.
		 */
		dbus_int32_t frequency =
			connman_network_get_uint16(network, "Frequency");

		if (frequency != 0)
			connman_dbus_dict_append_variant(dict, "frequency",
							 DBUS_TYPE_INT32,
							 &frequency);
		mode_val = SUPPLICANT_NETWORK_MODE_ADHOC;
	} else if (g_ascii_strcasecmp(mode_str, "hostap") == 0)
		mode_val = SUPPLICANT_NETWORK_MODE_HOSTAP;

	connman_dbus_dict_append_variant(dict, "mode",
					 DBUS_TYPE_INT32, &mode_val);
}

static gboolean is_dynamic_wep(const char *security,
    struct connman_network *network)
{
	const char *key_mgmt;

	if (g_ascii_strcasecmp(security, "wep") != 0)
		return FALSE;
	key_mgmt = connman_network_get_string(network,
	    CONNMAN_WIFI_EAP_KEY_MGMT);
	return (key_mgmt != NULL &&
	    g_strcmp0(key_mgmt, CONNMAN_WIFI_EAP_KEY_MGMT_1X) == 0);
}

/*
 * Append properties for the specified network to a dbus message.
 */
static void append_network_properties(struct supplicant_task *task,
	struct connman_network *network, DBusMessageIter *array)
{
	DBusMessageIter dict;
	dbus_uint32_t scan_ssid = 1;	/* NB: use directed ProbReq */
	const void *ssid;
	unsigned int ssid_len;
	const char *security;

	dbus_message_iter_open_container(array, DBUS_TYPE_ARRAY,
	    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
	        DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
	    DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	connman_dbus_dict_append_variant(&dict, "scan_ssid",
	    DBUS_TYPE_UINT32, &scan_ssid);

	ssid = connman_network_get_blob(network, "WiFi.SSID", &ssid_len);
	append_byte_array(&dict, "ssid", ssid, ssid_len);
	/* NB: do not set the bssid; this allows wpa_supplicant to roam */

	/* must configure background scanning for each netblock */
	append_bgscan(&dict, network);

	security = connman_network_get_string(network, "WiFi.Security");
	if (g_ascii_strcasecmp(security, "802_1x") == 0) {
		append_8021x(&dict, network);
        } else if (g_ascii_strcasecmp(security, "psk") == 0) {
		append_psk(&dict, network, "WPA RSN");
	} else if (g_ascii_strcasecmp(security, "wpa") == 0) {
		append_psk(&dict, network, "WPA");
	} else if (g_ascii_strcasecmp(security, "rsn") == 0) {
		append_psk(&dict, network, "RSN");
	} else if (is_dynamic_wep(security, network) == TRUE) {
		append_8021x(&dict, network);
	} else if (g_ascii_strcasecmp(security, "wep") == 0) {
		append_wep(&dict, network);
	} else {
		const char *key_mgmt = "NONE";
		connman_dbus_dict_append_variant(&dict, "key_mgmt",
		    DBUS_TYPE_STRING, &key_mgmt);
	}

	append_mode(&dict, network);

	dbus_message_iter_close_container(array, &dict);
}

/*
 * Add a new netblock with properties for the specified network.
 */
static char *network_add(struct supplicant_task *task,
	struct connman_network *network)
{
	const char *request = "AddNetwork";
	DBusMessage *message, *reply;
	DBusMessageIter iter;
	char *path;

	message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath,
	    SUPPLICANT_INTERFACE_INTF, request);
	if (message == NULL) {
		task_error(task, "%s: cannot allocate dbus msg", request);
		return NULL;
	}
	dbus_message_set_auto_start(message, FALSE);
	dbus_message_iter_init_append(message, &iter);
	append_network_properties(task, network, &iter);

	reply = send_call_and_block(task, message, __func__, request);
	if (reply == NULL)
		return NULL;

	path = (char *) get_objpath(task, reply, __func__, request);
	if (path != NULL) {
		path = g_strdup(path);
		_DBG_WIFI("path %s", path);
	}
	return path;
}

static struct DBusMessage *get_network_set_property_msg(
	struct supplicant_task *task, const char *func, const void *path,
	DBusMessageIter *iter)
{
	DBusMessage *message;
	const char *interface = SUPPLICANT_NETWORK_INTF;

	message = dbus_message_new_method_call(SUPPLICANT_NAME, path,
				DBUS_INTF ".Properties", "Set");
	if (message == NULL) {
		task_error(task, "%s: cannot allocate dbus msg", func);
		return NULL;
	}

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_iter_init_append(message, iter);
	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &interface);
	return message;
}

/*
 * Set Network.Properties for the specified network.
 */
static int network_set_properties(struct supplicant_task *task,
	struct connman_network *network, const void *handle)
{
	DBusMessage *message, *reply;
	const char *properties = "Properties";
	DBusMessageIter iter;

	_DBG_WIFI("task %p path %s", task, (const char *)handle);

	message = get_network_set_property_msg(task, __func__, handle, &iter);
	if (message == NULL)
		return -ENOMEM;

	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &properties);
	append_network_properties(task, network, &iter);

	reply = send_call_and_block(task, message, __func__, properties);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

/*
 * Set Network.<prop> for the specified network.
 */
static int network_set_property(struct supplicant_task *task,
	const void *path, const char *prop, int type, const void *val)
{
	DBusMessage *message, *reply;
	DBusMessageIter iter;

	_DBG_WIFI("task %p path %s prop %s", task, (const char *) path, prop);

	message = get_network_set_property_msg(task, __func__, path, &iter);
	if (message == NULL)
		return -ENOMEM;

	connman_dbus_property_append_variant(&iter, prop, type, val);

	reply = send_call_and_block(task, message, __func__, prop);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

/*
 * Tell the supplicant to select a network block as
 * the only one to use in processing scan results;
 * this implicitly starts a connection attempt.
 */
static int network_select(struct supplicant_task *task,
	const void *handle)
{
	const char *request = "SelectNetwork";
	DBusMessage *reply;

	reply = interface_send_request(task, request, handle);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

/*
 * Tell the supplicant to remove a network block.
 */
static int network_remove(struct supplicant_task *task,
	const void *handle)
{
	const char *request = "RemoveNetwork";
	DBusMessage *reply;

	reply = interface_send_request(task, request, handle);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

/*
 * Tell the supplicant to remove all network blocks.
 */
static int interface_remove_all(struct supplicant_task *task)
{
	const char *request = "RemoveAllNetworks";
	DBusMessage *reply;

	reply = interface_send_request(task, request, NULL);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

static int interface_flush_bss(struct supplicant_task *task, int age)
{
	const char *request = "FlushBSS";
	DBusMessage *message, *reply;

	message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath,
				SUPPLICANT_INTERFACE_INTF, request);
	if (message == NULL) {
		task_error(task, "%s: cannot allocate dbus msg", request);
		return -ENOMEM;
	}
	dbus_message_set_auto_start(message, FALSE);
	dbus_message_append_args(message,
	    DBUS_TYPE_UINT32, &age, DBUS_TYPE_INVALID);

	_DBG_WIFI("Interface.%s age %d", request, age);

	reply = send_call_and_block(task, message, __func__, request);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

/*
 * Tell the supplicant to enable a network block when
 * processing scan results.
 */
#if 0
static int network_enable(struct supplicant_task *task, const void *handle)
{
	dbus_bool_t enabled = TRUE;
	return network_set_property(task, handle, "Enabled",
	    DBUS_TYPE_BOOLEAN, &enabled);
}
#endif

/*
 * Tell the supplicant to disable a network block when
 * processing scan results.
 */
static int network_disable(struct supplicant_task *task, const void *handle)
{
	dbus_bool_t enabled = FALSE;
	return network_set_property(task, handle, "Enabled",
	    DBUS_TYPE_BOOLEAN, &enabled);
}

/*
 * Get a wpa_supplicant network block handle for a flimflam network reference
 */
static void *network_get_handle(struct supplicant_task *task,
				struct connman_network *network,
				const char **service_path_p)
{
	struct connman_service *service;
	const char *service_path;

	service = connman_service_lookup_from_network(network);
	if (service == NULL) {		/* XXX cannot happen */
		if (service_path_p != NULL)
			*service_path_p = NULL;
		return NULL;
	}

	service_path = connman_service_get_identifier(service);
	if (service_path_p != NULL)
		*service_path_p = service_path;

	return g_hash_table_lookup(task->netblocks, service_path);
}

static int interface_disconnect(struct supplicant_task *task)
{
	const char *request = "Disconnect";
	DBusMessage *reply;

	reply = interface_send_request(task, request, NULL);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

static int interface_clear_cached_credentials(struct supplicant_task *task)
{
	const char *request = "ClearCachedCredentials";
	DBusMessage *reply;

	reply = interface_send_request(task, request, NULL);
	if (reply == NULL)
		return -EIO;
	dbus_message_unref(reply);
	return 0;
}

static void scan_reply(DBusPendingCall *call, void *user_data)
{
	struct supplicant_task *task = user_data;
	DBusMessage *reply;

	_DBG_WIFI("task %p", task);

	task->scan_call = NULL;

	reply = dbus_pending_call_steal_reply(call);
	dbus_pending_call_unref(call);
	if (reply == NULL) {
		connman_device_set_scanning_state(task->device, FALSE);
		task_error(task, "%s: no reply", __func__);
		return;
	}
	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
		connman_device_set_scanning_state(task->device, FALSE);
		task_error(task, "%s: got error?", __func__);
		goto done;
	}

	/* scan actually started, mark our state to reflect */
	task->scanning = TRUE;
done:
	dbus_message_unref(reply);
}

static void append_blobs(DBusMessageIter *parent, void *arg)
{
	const GSList *blobs;
	DBusMessageIter array, element;

	_DBG_WIFI("parent = %p arg = %p", parent, arg);

	dbus_message_iter_open_container(parent, DBUS_TYPE_ARRAY,
	    DBUS_TYPE_ARRAY_AS_STRING
	    DBUS_TYPE_BYTE_AS_STRING,
	    &array);

	for (blobs = arg; blobs != NULL; blobs = g_slist_next(blobs)) {
		struct blob *blob = blobs->data;

		_DBG_WIFI("blob = %.*s", (int) blob->len, blob->data);

		/* blob signature: "ay" */
		dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
		    DBUS_TYPE_BYTE_AS_STRING, &element);
		dbus_message_iter_append_fixed_array(&element, DBUS_TYPE_BYTE,
		    &blob->data, blob->len);
		dbus_message_iter_close_container(&array, &element);
	}

	dbus_message_iter_close_container(parent, &array);
}

/*
 * Free all elements of a GSList starting with the (n+1)th element.
 * @list: GSList containing blobs
 * @n: n is an index, starting with 0 for the list head.
 */
static void g_slist_free_after(GSList *list, int n)
{
	GSList *nth, *e;

	nth = g_slist_nth(list, n);
	/* list is already shorter than n. */
	if (nth == NULL)
		return;

	/* Keep deleting (n+1)th element until none left */
	for (e = g_slist_next(nth); e != NULL; e = g_slist_next(nth)) {
		blob_free(e->data);
		nth = g_slist_delete_link (nth, e);
	}
}

static int build_scan_request(struct supplicant_scan_request *request)
{
	struct blob *broadcast_ssid;
	int ret;
	int ssid_count;

	_DBG_WIFI("request %p", request);

	request->scan_type = "active";

	request->ssids = NULL;
	connman_wifi_append_hidden_ssids(&request->ssids);

	if (request->ssids != NULL) {
		/*
		 * When scanning for specific Hidden SSIDs,
		 * always send the Broadcast SSID, too.
		 */
		ret = blob_new(&broadcast_ssid, 0, NULL);
		if (ret < 0)
			return ret;

		/*
		 * supplicant allows only SUPPLICANT_MAX_SSID_PER_SCAN ssids.
		 * So, send the first (WPAS_MAX_SSID_PER_SCAN - 1) in this list
		 * plus the broadcast SSID.
		 */
		ssid_count = g_slist_length(request->ssids);
		if (ssid_count >= SUPPLICANT_MAX_SSID_PER_SCAN) {
			/*
			 * TODO(djkurtz): revisit which ssids are chosen if too
			 * many.  This just arbitrarily includes first N in
			 * profile.
			 */
			connman_error("%s: %d ssids; using first %d only",
			    __func__, ssid_count,
			    SUPPLICANT_MAX_SSID_PER_SCAN-1);
			g_slist_free_after(request->ssids,
			    SUPPLICANT_MAX_SSID_PER_SCAN-2);
		}

		request->ssids = g_slist_append(request->ssids,
		    broadcast_ssid);
	}

	return 0;
}

static void free_scan_request(struct supplicant_scan_request *request)
{
	GSList *e;

	_DBG_WIFI("request %p", request);

	for (e=request->ssids; e != NULL; e=g_slist_next(e))
		blob_free(e->data);
	g_slist_free(request->ssids);
}

/*
 * Request the supplicant do an active scan on the interface.
 */
static int interface_scan(struct supplicant_task *task)
{
	DBusMessage *message;
	DBusMessageIter array, dict;
	struct supplicant_scan_request request;
	int ret;

	_DBG_WIFI("task %p", task);

	if (task->ifpath == NULL)
		return -EINVAL;

	if (task->scan_call != NULL)
		return -EALREADY;

	ret = build_scan_request(&request);
	if (ret < 0) {
		free_scan_request(&request);
		return ret;
	}

	message = dbus_message_new_method_call(SUPPLICANT_NAME, task->ifpath,
					SUPPLICANT_INTERFACE_INTF, "Scan");
	if (message == NULL) {
		free_scan_request(&request);
		return -ENOMEM;
	}

	dbus_message_set_auto_start(message, FALSE);
	dbus_message_iter_init_append(message, &array);
	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
	    DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
	    DBUS_TYPE_STRING_AS_STRING
	    DBUS_TYPE_VARIANT_AS_STRING
	    DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
	    &dict);

	connman_dbus_dict_append_variant(&dict, "Type", DBUS_TYPE_STRING,
	    &request.scan_type);

	/* SSIDs: an array of byte arrays "aay" */
	if (request.ssids != NULL)
		connman_dbus_dict_append_variant_container(&dict, "SSIDs",
		    DBUS_TYPE_ARRAY_AS_STRING
		    DBUS_TYPE_ARRAY_AS_STRING
		    DBUS_TYPE_BYTE_AS_STRING,
		    append_blobs, request.ssids);

	dbus_message_iter_close_container(&array, &dict);

	free_scan_request(&request);

	/* NB: do here, the reply is slow and we want the ui to update asap */
	connman_device_set_scanning_state(task->device, TRUE);

	return send_call2(task, message, scan_reply, &task->scan_call,
	    __func__, "interface scan");
}

/*
 * Construct the identifier for a network from the bssid and ssid.
 * We intentionally construct a valid d-bus object path because
 * the constructed string is eventually used this way.
 */
static void make_objpath(struct supplicant_result *result)
{
	int i, off;

	CONNMAN_ASSERT(2*(ETH_ALEN + result->ssid_len) < sizeof(result->path));

	memset(result->path, 0, sizeof(result->path));
	/*
	 * NB: snprintf(.., 3) works 'cuz we know path has room
	 * for the trailing byte.
	 * */
	for (i = 0, off = 0; i < ETH_ALEN; i++, off += 2)
		snprintf(&result->path[off], 3, "%02X", result->addr[i]);
	for (i = 0; i < result->ssid_len; i++, off += 2)
		snprintf(&result->path[off], 3, "%02x", result->ssid[i]);
}

/*
 * Extract address information from the scan results
 * and construct the object path to be used later
 * for the BSS.
 */
static void extract_addr(struct supplicant_task *task,
	DBusMessageIter *value, struct supplicant_result *result)
{
	DBusMessageIter array;
	unsigned char *addr;
	int addr_len;

	dbus_message_iter_recurse(value, &array);
	dbus_message_iter_get_fixed_array(&array, &addr, &addr_len);

	if (addr_len != ETH_ALEN) {
		task_error(task, "%s: unexpected address length %d, got %d",
		    __func__, ETH_ALEN, addr_len);
		return;
	}

	memcpy(result->addr, addr, ETH_ALEN);
	make_objpath(result);
}

/*
 * Extract the SSID from the scan results and construct
 * a name for the BSS by converting any non-printing
 * characters to spaces.
 */
static void extract_ssid(struct supplicant_task *task,
	DBusMessageIter *value, struct supplicant_result *result)
{
	DBusMessageIter array;
	unsigned char *ssid;
	int ssid_len;

	dbus_message_iter_recurse(value, &array);
	dbus_message_iter_get_fixed_array(&array, &ssid, &ssid_len);

	if (ssid_len > MAX_SSID_LEN) {
		task_error(task, "%s: ssid len %d", __func__, ssid_len);
		return;
	}
	if (ssid_len < 1 || ssid[0] == '\0') {
		_DBG_WIFI("no/blank ssid");
		return;
	}

	memcpy(result->ssid, ssid, ssid_len);
	result->ssid_len = ssid_len;

	connman_network_get_name_from_id(ssid, ssid_len, result->name,
					 sizeof(result->name));
}

/*
 * Check the key management support reported in the scan
 * result and mark whether 802.1x support is present.  The
 * returned value indicates if WPA-PSK is supported and
 * used by the caller to mark either WPA RSN PSK support.
 *
 * TODO(sleffler) there's no reason to distinguish between
 *     WPA and RSN.
 */
static gboolean check_keymgmt(struct supplicant_task *task,
	DBusMessageIter *iter, struct supplicant_result *result)
{
	DBusMessageIter array;
	const char *key_mgmt;
	gboolean has_802_1x = FALSE;
	gboolean has_psk = FALSE;

	dbus_message_iter_recurse(iter, &array);
	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		dbus_message_iter_get_basic(&array, &key_mgmt);
		if (g_strstr_len(key_mgmt, -1, "-eap") != NULL)
			has_802_1x = TRUE;
		if (g_strstr_len(key_mgmt, -1, "-psk") != NULL)
			has_psk = TRUE;
		/* TODO(sleffler) how should wpa-none be handled? */
		dbus_message_iter_next(&array);
	}
	if (has_802_1x)
		result->has_802_1x = TRUE;

	return (has_802_1x || has_psk);
}

/*
 * Extract key management information from the scan results.
 * The results are interpreted to identify if PSK and 802.1x
 * support are present.
 */
static gboolean extract_keymgmt(struct supplicant_task *task,
	DBusMessageIter *iter, struct supplicant_result *result)
{
	DBusMessageIter dict;

	dbus_message_iter_recurse(iter, &dict);

	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&dict, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		if (g_str_equal(key, "KeyMgmt") == TRUE) {
			dbus_message_iter_next(&entry);
			dbus_message_iter_recurse(&entry, &value);
			return check_keymgmt(task, &value, result);
		}
		dbus_message_iter_next(&entry);
	}
	return FALSE;		/* IE present but no key mgmt */
}

/*
 * Extract the setting of the PRIVACY capability for the
 * BSS from the scan result.
 */
static dbus_bool_t extract_privacy(struct supplicant_task *task,
	DBusMessageIter *value)
{
	dbus_bool_t privacy;

	DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_BOOLEAN, return FALSE);
	dbus_message_iter_get_basic(value, &privacy);

	return privacy;
}

/*
 * Extract the BSS operating mode for the BSS from the dbus msg.
 * We only handle IBSS and Infrastructure modes (e.g. no mesh).
 */
static const char *extract_mode(struct supplicant_task *task,
	DBusMessageIter *value)
{
	const char *mode;

	DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_STRING, return NULL);
	dbus_message_iter_get_basic(value, &mode);

	if (g_str_equal(mode, "infrastructure") == TRUE)
		return "managed";
	else if (g_str_equal(mode, "ad-hoc") == TRUE)
		return "adhoc";
	else {
		connman_error("%s: unknown BSS mode %s", __func__, mode);
		return NULL;		/* XXX ? */
	}
}

/*
 * Extract the signal strength data for the BSS from the dbus msg.
 */
static dbus_int16_t extract_signal(struct supplicant_task *task,
	DBusMessageIter *value)
{
	dbus_int16_t signal;

	DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_INT16, return -1);
	dbus_message_iter_get_basic(value, &signal);

	return signal;
}

/*
 * Extract the channel frequency for the BSS from the dbus msg.
 */
static dbus_uint16_t extract_frequency(struct supplicant_task *task,
	DBusMessageIter *value)
{
	dbus_uint16_t frequency;

	DBUS_EXPECT_ITER_ARG(value, DBUS_TYPE_UINT16, return 0);
	dbus_message_iter_get_basic(value, &frequency);

	return frequency;
}

/*
 * Extract rate information for the BSS from the scan result.
 * We use the rate set to calculate the max data rate.
 */
static void extract_rates(struct supplicant_task *task,
	DBusMessageIter *value, struct supplicant_result *result)
{
	DBusMessageIter array;
	const dbus_uint32_t *rates;
	int rates_len;

	/* TODO(sleffler) validate is fixed and type */
	dbus_message_iter_recurse(value, &array);
	dbus_message_iter_get_fixed_array(&array, &rates, &rates_len);

	/* NB: rates array is sorted so max is first */
	result->maxrate = (rates_len > 0 ? rates[0] : 0);
}

/*
 * Extract miscellaneous info from the BSS's Information Elements.
 * We scan these to determine capabilities such as HT and ERP;
 * to calculate a "phy mode" for the BSS channel.
 */
static void extract_ie_info(struct supplicant_task *task,
	DBusMessageIter *value, struct supplicant_result *result)
{
	DBusMessageIter array;
	const dbus_uint32_t *ie_data;
	const uint8_t *ie, *ie_end;
	int ie_data_len;

	/* TODO(sleffler) validate is fixed and type */
	dbus_message_iter_recurse(value, &array);
	dbus_message_iter_get_fixed_array(&array, &ie_data, &ie_data_len);

	ie = (const uint8_t *) ie_data;
	ie_end = ie + ie_data_len;
	while (ie_end - ie > 1) {
		if (ie_end - ie < 2 + ie[1]) {
			connman_error("%s: ie length %d at offset %d "
			    "extends past container length %d",
			    __func__, ie[1],
			    (int) (ie - (const uint8_t *) ie_data),
			    ie_data_len);
			return;
		}
		if (ie[0] == IEEE80211_ELEMID_ERP) {
			result->phymode = CONNMAN_NETWORK_PHYMODE_11G;
			/* NB: continue to check for HT */
		}
		if (ie[0] == IEEE80211_ELEMID_HTCAP ||
		    ie[0] == IEEE80211_ELEMID_HTINFO) {
			result->phymode = CONNMAN_NETWORK_PHYMODE_11N;
			return;
		}
		/* TODO(sleffler) check VENDOR ie's for compat HT and Atheros */
		ie += 2+ie[1];
	}
}

/*
 * Convert a signal (in dBm) to a strength value in the range [0..100].
 */
static unsigned char calculate_strength(struct supplicant_task *task,
	int signal)
{
	unsigned char strength;

	if (signal > 0)
		strength = 100 - signal;
	else
		strength = 120 + signal;
	return (strength < 100 ? strength : 100);
}

/*
 * Calculate an IEEE channel # given a frequency.  This is
 * necessarily a bit heuristic as we do not have information
 * such as the regulatory state to make certain decisions.
 */
static unsigned short calculate_channel(struct supplicant_result *result)
{
	if (result->frequency < 0)
		return 0;

	if (result->frequency == 2484)
		return 14;
	if (result->frequency < 2484)
		return (result->frequency - 2407) / 5;
	if (result->frequency < 5000) {
		/* NB: Public Safety Band (PSB) is 4940..4990 */
		if (result->frequency >= 4940 && result->frequency <= 4990)
			return 37 + ((result->frequency * 10) +
			    ((result->frequency % 5) == 2 ? 5 : 0) - 49400) / 5;
		if (result->frequency > 4900)
			return (result->frequency - 4000) / 5;
		else
			return 15 + (result->frequency - 2512) / 20;
	}
	return (result->frequency - 5000) / 5;
}

/*
 * Convert the BSS properties received over the DBus from wpa_supplicant
 * to the supplicant_result state block.  We return FALSE if something
 * was wrong that makes the result unusable; otherwise TRUE.
 */
static gboolean extract_bss_properties(struct supplicant_task *task,
	struct supplicant_result *result, DBusMessageIter *iter)
{

	memset(result, 0, sizeof(*result));
	result->frequency = -1;
	result->signal = -1;
	result->phymode = CONNMAN_NETWORK_PHYMODE_UNDEF;

	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(iter, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (g_str_equal(key, "BSSID") == TRUE)
			extract_addr(task, &value, result);
		else if (g_str_equal(key, "SSID") == TRUE)
			extract_ssid(task, &value, result);
		else if (g_str_equal(key, "Mode") == TRUE)
			result->mode = extract_mode(task, &value);
		else if (g_str_equal(key, "Privacy") == TRUE)
			result->privacy = extract_privacy(task, &value);
		else if (g_str_equal(key, "Frequency") == TRUE)
			result->frequency = extract_frequency(task, &value);
		else if (g_str_equal(key, "Signal") == TRUE)
			result->signal = extract_signal(task, &value);
		else if (g_str_equal(key, "Rates") == TRUE)
			extract_rates(task, &value, result);
		else if (g_str_equal(key, "WPA") == TRUE)
			result->has_wpa_psk =
			    extract_keymgmt(task, &value, result);
		else if (g_str_equal(key, "RSN") == TRUE)
			result->has_rsn_psk =
			    extract_keymgmt(task, &value, result);
		else if (g_str_equal(key, "IEs") == TRUE)
			extract_ie_info(task, &value, result);

		dbus_message_iter_next(iter);
	}
	if (result->path[0] == '\0') {
		task_error(task, "%s: ignore BSS, no BSSID", __func__);
		return FALSE;
	}
	if (result->mode == NULL) {
		task_error(task, "%s: ignore BSS, no mode", __func__);
		return FALSE;
	}
	if (result->frequency < 0) {
		task_error(task, "%s: ignore BSS, invalid frequency %d",
		    __func__, result->frequency);
		return FALSE;
	}

	result->channel = calculate_channel(result);
	if (result->phymode == CONNMAN_NETWORK_PHYMODE_UNDEF) {
		/*
		 * Intuit a phy mode if extract_ie_info wasn't able to.
		 * NB: 3000 here is just slop, could be more precise.
		 */
		if (result->frequency < 3000) {
			/*
			 * 2.4GHZ legacy, check tx rate for 11b-only
			 * (note 22M is valid)
			 */
			if (result->maxrate < 24000000)
				result->phymode = CONNMAN_NETWORK_PHYMODE_11B;
			else
				result->phymode = CONNMAN_NETWORK_PHYMODE_11G;
		} else
			result->phymode = CONNMAN_NETWORK_PHYMODE_11A;
	}
	result->strength = calculate_strength(task, result->signal);

	if (result->has_802_1x == TRUE)
		result->security = "802_1x";
	else if (result->has_rsn_psk == TRUE)
		result->security = "rsn";
	else if (result->has_wpa_psk == TRUE)
		result->security = "wpa";
	else if (result->privacy == TRUE)
		result->security = "wep";
	else
		result->security = "none";

	return TRUE;
}

static void update_strength(struct supplicant_task *task,
	struct connman_network *network, unsigned short strength)
{
	connman_network_set_scangen(network, task->scangen);
	connman_network_set_strength(network, strength);
}

/*
 * Add/update a BSS entry based on the supplied scan result.
 * We construct a network object (or use an existing one)
 * and record relevant state.
 */
static struct connman_network *add_bss(struct supplicant_task *task,
	struct supplicant_result *bss)
{
	struct connman_network *network;
	char *group;

	group = connman_wifi_build_group(bss->path, bss->name,
	    bss->ssid, bss->ssid_len, bss->mode, bss->security);

	network = connman_device_get_network(task->device, bss->path);

	_DBG_WIFI("%s %s \"%s\" (%s %s%s) signal %d strength %u freq %u",
	    network == NULL ? "add" : "update", bss->path, bss->name,
	    bss->mode, bss->security, (bss->has_wps == TRUE) ? " WPS" : "",
	    bss->signal, bss->strength, bss->frequency);

	if (network == NULL) {
		network = connman_network_create(bss->path,
		    CONNMAN_NETWORK_TYPE_WIFI);
		if (network == NULL) {
			task_error(task, "%s: cannot create network %s",
			    __func__, bss->name);
			goto done;
		}

		connman_network_set_index(network,
		    connman_device_get_index(task->device));
		connman_network_set_protocol(network,
		    CONNMAN_NETWORK_PROTOCOL_IP);
		connman_network_set_address(network, bss->addr, ETH_ALEN);

		if (connman_device_add_network(task->device, network) < 0) {
			task_error(task, "%s: cannot add network %s",
			    __func__, bss->name);
			connman_network_unref(network);
			network = NULL;
			goto done;
		}
	}

	if (bss->name[0] != '\0')
		connman_network_set_name(network, bss->name);
	/* TODO(sleffler) move to create case? */
	connman_network_set_blob(network, "WiFi.SSID",
	    bss->ssid, bss->ssid_len);
	connman_network_set_string(network, "WiFi.Mode", bss->mode);
	update_strength(task, network, bss->strength);
	connman_network_set_uint16(network, "Frequency", bss->frequency);
	connman_network_set_uint16(network, "WiFi.Channel", bss->channel);
	connman_network_set_phymode(network, bss->phymode);
	connman_network_set_string(network, "WiFi.Security", bss->security);

	connman_network_set_available(network, TRUE);

	/* NB: do last since this may cause the profile to be updated */
	if (bss->ssid[0] != 0)
		connman_network_set_group(network, group);
done:
	g_free(group);

	return network;
}

/*
 * Handle an Interface.BSSAdded signal from wpa_supplicant.
 */
static void interface_signal_bss_added(struct supplicant_task *task,
	DBusMessage *msg)
{
	DBusMessageIter iter, dict;
	struct connman_network *network;
	const char *path;
	struct supplicant_result result;

	dbus_message_iter_init(msg, &iter);
	DBUS_EXPECT_ITER_ARG(&iter, DBUS_TYPE_OBJECT_PATH, return);
	dbus_message_iter_get_basic(&iter, &path);
	dbus_message_iter_next(&iter);
	DBUS_EXPECT_ITER_ARG(&iter, DBUS_TYPE_ARRAY, return);
	dbus_message_iter_recurse(&iter, &dict);

	if (extract_bss_properties(task, &result, &dict) == FALSE)
		return;

	network = add_bss(task, &result);
	if (network != NULL) {
		void *handle = g_hash_table_lookup(task->bss, path);
		if (handle == NULL) {
			_DBG_WIFI("add BSS %s network %p", path, network);
			g_hash_table_insert(task->bss,
			    (gpointer) g_strdup(path),
			    connman_network_ref(network));
		} else if (handle != network) {
			task_error(task, "%s: new BSS %s already in table "
			    "(replace %p by %p)!", __func__, path,
			    handle, network);
			g_hash_table_replace(task->bss,
			    (gpointer) g_strdup(path),
			    connman_network_ref(network));
		}
	}
}

/*
 * Handle an Interface.BSSRemoved signal from wpa_supplicant.
 */
static void interface_signal_bss_removed(struct supplicant_task *task,
	DBusMessage *msg)
{
	DBusError error;
	const char *path;
	void *handle;

	dbus_error_init(&error);
	if (dbus_message_get_args(msg, &error, DBUS_TYPE_OBJECT_PATH, &path,
						DBUS_TYPE_INVALID) == FALSE) {
		if (dbus_error_is_set(&error) == TRUE) {
			task_error(task, "%s: %s", __func__, error.message);
			dbus_error_free(&error);
		} else
			task_error(task, "%s: wrong arguments", __func__);
		return;
	}

	handle = g_hash_table_lookup(task->bss, path);
	if (handle != NULL) {
		_DBG_WIFI("remove BSS %s network %p", path, handle);
		/*
		 * Be careful to clear any reference outside the bss table.
		 * There may be a connection request in flight (e.g. due to
		 * auto connect) in which case we need to clear task->network
		 * but there's no need to otherwise alter its state (e.g. w/
		 * connman_network_set_disconnected) because removing it
		 * from the hash table will cause it to be removed from the
		 * device and that in turns clears all state.  The only other
		 * references (task->current_bss and task->pending_network)
		 * cannot be set without our being notified (e.g. a bss
		 * change signal for current_bss).
		 */
		if (task->network == handle) {
			void *netblock;

			_DBG_WIFI("clear pending connect request");
			/*
			 * Make sure the network has its supplicant
			 * netblock disabled or it may try to associate
			 * on the next scan.
			 */
			netblock = network_get_handle(task, task->network,
			    NULL);
			if (netblock != NULL)
				network_disable(task, netblock);

			connman_network_unref(task->network);
			task->network = NULL;

			/*
			 * NB: autconnect is deferred to the idle loop
			 * which is important as we want it to happen
			 * after we've released this BSS which we're trying
			 * to connect to (this insures if we try a new
			 * connection it'll be to a different service).
			 */
			connman_device_auto_connect(task->device);
		}
		g_hash_table_remove(task->bss, path);
	} else
		_DBG_WIFI("%s not in BSS table", path);
}

static enum supplicant_state string2state(const char *state)
{
	if (g_ascii_strcasecmp(state, "INACTIVE") == 0)
		return WPA_INACTIVE;
	else if (g_ascii_strcasecmp(state, "SCANNING") == 0)
		return WPA_SCANNING;
	else if (g_ascii_strcasecmp(state, "AUTHENTICATING") == 0)
		return WPA_AUTHENTICATING;
	else if (g_ascii_strcasecmp(state, "ASSOCIATING") == 0)
		return WPA_ASSOCIATING;
	else if (g_ascii_strcasecmp(state, "ASSOCIATED") == 0)
		return WPA_ASSOCIATED;
	else if (g_ascii_strcasecmp(state, "GROUP_HANDSHAKE") == 0)
		return WPA_GROUP_HANDSHAKE;
	else if (g_ascii_strcasecmp(state, "4WAY_HANDSHAKE") == 0)
		return WPA_4WAY_HANDSHAKE;
	else if (g_ascii_strcasecmp(state, "COMPLETED") == 0)
		return WPA_COMPLETED;
	else if (g_ascii_strcasecmp(state, "DISCONNECTED") == 0)
		return WPA_DISCONNECTED;
	else
		return WPA_INVALID;
}

static void
handle_pending(struct supplicant_task *task)
{
	struct connman_network *network;
	int err;

	_DBG_WIFI("current_bss %p network %p pending_network %p",
	    task->current_bss, task->network, task->pending_network);

	if (task->pending_network == NULL)
		return;
	network = task->pending_network;
	task->pending_network = NULL;
	err = task_connect(task, network);
	/* NB: release pending_network ref */
	connman_network_unref(network);

	if (err < 0 && err != -EINPROGRESS)
		task_error(task, "%s: task_connect failed, error %d",
		   __func__, err);
}

static int is_psk(struct connman_network *network)
{
	const char *security =
	    connman_network_get_string(network, "WiFi.Security");
        return (g_ascii_strcasecmp(security, "wpa") == 0 ||
	        g_ascii_strcasecmp(security, "rsn") == 0);
}

/*
 * Handle a supplicant state change.
 */
static void state_change(struct supplicant_task *task,
	enum supplicant_state newstate)
{
	enum supplicant_state ostate = task->state;
	struct connman_network *network;

	/*
	 * State changes refer to the network we're trying to connect to,
	 * otherwise the current BSS.  This is a byproduct of our being
	 * signaled of CurrentBSS changes only once the state machine reaches
	 * the COMPLETED state.
	 */
	if (task->network != NULL)
		network = task->network;
	else
		network = task->current_bss;

	task_info(task, "state change %s -> %s%s (BSSID %s)",
	    supplicant_state_names[ostate],
	    supplicant_state_names[newstate],
	    task->scanning == TRUE ? " (scanning)" : "",
            network != NULL ?
	        connman_network_get_string(network, "Address") : "<NA>"
	);

	task->state = newstate;

	if (network == NULL) {
		_DBG_WIFI("no network");
		return;
	}

	/*
	 * Update internal (network) state machine.  This follows the
	 * wpa_supplicant state machine but with some intermediate states
	 * ignored:
	 *
	 * WPA_DISCONNECTED	NETWORK_STATE_IDLE
	 * WPA_INACTIVE		NETWORK_STATE_IDLE
	 * WPA_SCANNING		NETWORK_STATE_CONNECTING
	 * WPA_AUTHENTICATING	NETWORK_STATE_ASSOCIATING
	 * WPA_ASSOCIATING	NETWORK_STATE_ASSOCIATING
	 * WPA_ASSOCIATED	NETWORK_STATE_ASSOCIATING
	 * WPA_4WAY_HANDSHAKE	NETWORK_STATE_ASSOCIATING
	 * WPA_GROUP_HANDSHAKE	NETWORK_STATE_ASSOCIATING
	 * WPA_COMPLETED	NETWORK_STATE_CONNECTED
	 *
	 * Beware that wpa_supplicant will coalesce state changes so we
	 * only see the latest state.  This makes our work almost a guess.
	 *
	 * Note that the UI operates based on the service state machine
	 * which is driven, for wifi networks, from the network state
	 * and, once connected, the ipconfig state (e.g. dhclient).
	 */
	switch (newstate) {
	case WPA_COMPLETED:
		/* carrier on */
		connman_network_set_connected(network);
		break;

	case WPA_ASSOCIATED:
		connman_network_set_associating(network);
		break;

	case WPA_AUTHENTICATING:
	case WPA_ASSOCIATING:
	case WPA_4WAY_HANDSHAKE:
	case WPA_GROUP_HANDSHAKE:
		if (ostate != WPA_COMPLETED)
			connman_network_set_associating(network);
		break;

	default:
		break;
	}
}

/*
 * Handle an Interface.PropertiesChanged.Scanning signal from wpa_supplicant.
 */
static void interface_property_changed_scanning(struct supplicant_task *task,
	dbus_bool_t scanning)
{
	_DBG_WIFI("Scanning %d (task->scanning %d scangen %d)", scanning,
	    task->scanning, task->scangen);

	connman_device_set_scanning_state(task->device, scanning);

	if (scanning == TRUE) {
		/*
		 * Scan started, bump the scangen so signal strength
		 * data get pushed from the network to the service.
		 */
		task->scangen++;
	}
	if (task->scanning == TRUE && scanning == FALSE) {
		/*
		 * We requested the scan and it's just completed;
		 * potentially auto-connect using the results.
		 */
		task->scanning = FALSE;
		connman_device_auto_connect(task->device);
	}

	if (scanning == FALSE && task->flush_pending == TRUE) {
		time_t age;
		/*
		 * Scan completed and we have a pending flush BSS
		 * request.  Age out entries using a time that is
		 * 10 secs more than the time since we resumed (10
		 * is fairly arbitrary; it's based on the idea you
		 * won't move the device very far in that time).
		 *
		 * NB: We don't need to explicitly flush our local
		 * state (as done w/ the old dbus api) because we
		 * receive a signal for each BSS to be removed.
		 */
		task->flush_pending = FALSE;
		age = (time(NULL) - task->resume_time) + 10;
		(void) interface_flush_bss(task, age);
	}
}

static void unref_current_bss(struct supplicant_task *task)
{
	connman_network_set_string(task->current_bss,
	    CONNMAN_WIFI_AUTHMODE, NULL);
	connman_network_unref(task->current_bss);
	task->current_bss = NULL;
}

/*
 * Check for a 4WAY_HANDSHAKE failure that might be due to
 * an incorrect user-entered passphrase.
 */
static int check_4way_handshake(struct supplicant_task *task)
{
	if (!(task->state == WPA_4WAY_HANDSHAKE && is_psk(task->current_bss)))
		return FALSE;
	if (connman_network_get_in_use(task->current_bss) == FALSE) {
		/*
		 * This happens if we are called when disconnecting; e.g.
		 * on connect timeout the core calls in to disconnect and
		 * we may interpret the currentBSS change to be a 4-way
		 * handshake failure.  Ignore the possible error as it's
		 * not meaningful (could've happened for any reason as we
		 * interrupted the state machine).
		 */
		return FALSE;
	}

	task_error(task,"%s: 4-Way handshake failed - shared key may be "
	    "incorrect", __func__);

	if (connman_network_get_previously_connected(task->current_bss)
		 == FALSE) {
		/*
		 * We've never successfully joined this network; treat
		 * the failure as a bad pasphrase and stop trying to
		 * connect.
		 */
		connman_network_set_error(task->current_bss,
		    CONNMAN_ELEMENT_ERROR_BAD_PASSPHRASE);
		return FALSE;
	} else {
		/*
		 * We've previously connected with this passphrase,
		 * ignore the error and direct the caller to keep
		 * trying.
		 */
		return TRUE;
	}
}

/*
 * Handle an Interface.PropertiesChanged.CurrentBSS signal from wpa_supplicant.
 */
static void interface_property_changed_current_bss(struct supplicant_task *task,
	const char *path)
{
	void *handle;
	struct connman_network *network;

	if (g_str_equal(path, "/") == TRUE) {
		/*
		 * A path of "/" means no current BSS.  Clear the held
		 * reference and fulfill any connect request that got
		 * queued up behind a disconnect.
		 */
		_DBG_WIFI("CurrentBSS %s current_bss %p network %p "
		    "pending_network %p", path, task->current_bss,
		    task->network, task->pending_network);
		if (task->current_bss != NULL) {
			/*
			 * If there is no new network and we got here by way
			 * of 4WAY_HANDSHAKE, it is possible the user-entered
			 * passphrase was incorrect.
			 */
			if (task->network == NULL &&
			    check_4way_handshake(task) == TRUE) {
				/* ignore 4-way handshake failure */
				handle_pending(task);
				return;
			}

			/*
			 * Make sure the outgoing network has its supplicant
			 * netblock disabled.
			 */
			handle = network_get_handle(task, task->current_bss,
						    NULL);
			if (handle != NULL)
				network_disable(task, handle);

			connman_network_set_disconnected(task->current_bss);
			unref_current_bss(task);
		}
		handle_pending(task);
		return;
	}

	handle = g_hash_table_lookup(task->bss, path);
	if (handle == NULL) {
		_DBG_WIFI("CurrentBSS %s not in BSS table", path);
		/* XXX what do we do? */
		return;
	}
	network = handle;

	/*
	 * Roaming case: mark the old network disconnected
	 * (but not the associated service or device) and
	 * install the new network as ``current''.
	 */
	_DBG_WIFI("CurrentBSS %s current_bss %p -> %p (network %p)", path,
	    task->current_bss, network, task->network);

	if (network == task->current_bss) {
		/*
		 * This seems to happen sometimes when roaming; we get
		 * notified of a BSS change after auth and then again
		 * after assoc.  Just avoid the state churn.
		 */
		return;
	}

	if (task->current_bss != NULL) {
		connman_network_set_disconnected_only(task->current_bss);
		unref_current_bss(task);
	}
	task->current_bss = connman_network_ref(network);
	if (task->network != NULL) {
		/*
		 * Cleanup connection request state held in task->network.
		 * We hold a ref to the network object between the time we
		 * initiate a connection and the point we actually join the
		 * network (at which point it's moved to the current_bss).
		 * In particular handle the case of a dbus request to connect
		 * that generates a ``hidden network'' that's constructed with
		 * the user-supplied parameters.  This network object is
		 * different from the one we actually install as the
		 * current_bss and we need to discard the original hidden
		 * network so it doesn't get re-used for things like
		 * auto-connect.  The core code recognizes a hidden network
		 * and cleans up the device and service state.
		 */
		if (task->network != task->current_bss)
			connman_network_set_disconnected_only(task->network);
		connman_network_unref(task->network);
		task->network = NULL;
	}

	/* sync service state to reflect ``current network'' */
	connman_service_set_current_network(task->current_bss);
}

/*
 * Handle an Interface.PropertiesChanged signal from wpa_supplicant.
 */
static void interface_signal_properties_changed(struct supplicant_task *task,
	DBusMessage *msg)
{
	DBusMessageIter array, dict;
	const char *state;

	dbus_message_iter_init(msg, &array);
	DBUS_EXPECT_ITER_ARG(&array, DBUS_TYPE_ARRAY, return);
	dbus_message_iter_recurse(&array, &dict);

	state = NULL;
	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&dict, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (g_str_equal(key, "Scanning") == TRUE) {
			dbus_bool_t scanning;

			dbus_message_iter_get_basic(&value, &scanning);
			interface_property_changed_scanning(task, scanning);
		} else if (g_str_equal(key, "State") == TRUE) {
			dbus_message_iter_get_basic(&value, &state);
			_DBG_WIFI("State %s", state);
			/* NB: defer so we handle any CurrentBSS change first */
		} else if (g_str_equal(key, "CurrentBSS") == TRUE) {
			const char *path;

			dbus_message_iter_get_basic(&value, &path);
			interface_property_changed_current_bss(task, path);
		} else if (g_str_equal(key, "CurrentAuthMode") == TRUE) {
			const char *authmode;

			dbus_message_iter_get_basic(&value, &authmode);
			if (task->current_bss != NULL)
				connman_network_set_string(task->current_bss,
				    CONNMAN_WIFI_AUTHMODE, authmode);
#if 0
		} else if (g_str_equal(key, "CurrentNetwork") == TRUE) {
		} else if (g_str_equal(key, "Signal") == TRUE) {
#endif
		}

		dbus_message_iter_next(&dict);
	}
	if (state != NULL)
		state_change(task, string2state(state));
}

/*
 * Filter fi.w1.wpa_supplicant1.Interface signals from the supplicant.
 */
static DBusHandlerResult interface_filter(DBusMessage *msg, void *data)
{
	struct supplicant_task *task;
	const char *member, *path;

	member = dbus_message_get_member(msg);
	if (member == NULL) {
		_DBG_WIFI("no msg member (interface=%s, sender=%s)",
		    dbus_message_get_interface(msg),
		    dbus_message_get_sender(msg));
		_DBG_WIFI("no msg member");
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	path = dbus_message_get_path(msg);
	if (path == NULL) {
		_DBG_WIFI("no path (interface=%s member=%s, sender=%s)",
		    dbus_message_get_interface(msg), member,
		    dbus_message_get_sender(msg));
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	task = find_task_by_path(path);
	if (task == NULL) {
		/* NB: this can fire a bunch after removing an interface */
		_DBG_WIFI("no task for path %s "
		    "(interface=%s, member=%s, sender=%s)",
		    path, dbus_message_get_interface(msg), member,
		    dbus_message_get_sender(msg));
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	/* NB: sorted alphabetically; not sure if it matters */
	if (g_str_equal(member, "BSSAdded") == TRUE)
		interface_signal_bss_added(task, msg);
	else if (g_str_equal(member, "BSSRemoved") == TRUE)
		interface_signal_bss_removed(task, msg);
	else if (g_str_equal(member, "PropertiesChanged") == TRUE)
		interface_signal_properties_changed(task, msg);
	else if (g_str_equal(member, "ScanDone") == TRUE)
		/* NB: ignore, monitor Interface.Scanning property */;
	else
		_DBG_WIFI("Interface.%s not handled", member);

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

/*
 * Handle a BSS.PropertiesChanged signal from wpa_supplicant.
 */
static void bss_signal_properties_changed(struct supplicant_task *task,
	struct connman_network *network, DBusMessage *msg)
{
	DBusMessageIter array, dict;

	dbus_message_iter_init(msg, &array);
	DBUS_EXPECT_ITER_ARG(&array, DBUS_TYPE_ARRAY, return);
	dbus_message_iter_recurse(&array, &dict);

	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&dict, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (g_str_equal(key, "Signal") == TRUE) {
			dbus_int16_t signal = extract_signal(task, &value);

			_DBG_WIFI("%s (%s) signal %d",
			    connman_network_get_string(network, "Name"),
			    connman_network_get_string(network, "Address"),
			    signal);
			if (signal != -1)
				update_strength(task, network, 
				    calculate_strength(task, signal));
		} else if (g_str_equal(key, "Frequency") == TRUE) {
			dbus_int16_t frequency = extract_frequency(task, &value);

			_DBG_WIFI("%s (%s) frequency %d",
			    connman_network_get_string(network, "Name"),
			    connman_network_get_string(network, "Address"),
			    frequency);
			if (frequency > 0)
				connman_network_set_uint16(network, "Frequency",
				    frequency);
		}

		dbus_message_iter_next(&dict);
	}
}

/*
 * Filter fi.w1.wpa_supplicant1.BSS signals from the supplicant.
 */
static DBusHandlerResult bss_filter(DBusMessage *msg, void *data)
{
	struct supplicant_task *task;
	const char *member, *path;
	struct connman_network *network;

	member = dbus_message_get_member(msg);
	if (member == NULL) {
		_DBG_WIFI("no msg member (interface=%s, sender=%s)",
		    dbus_message_get_interface(msg),
		    dbus_message_get_sender(msg));
		_DBG_WIFI("no msg member");
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	path = dbus_message_get_path(msg);
	if (path == NULL) {
		_DBG_WIFI("no path (interface=%s member=%s, sender=%s)",
		    dbus_message_get_interface(msg), member,
		    dbus_message_get_sender(msg));
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	task = find_task_by_path(path);
	if (task == NULL) {
		_DBG_WIFI("no task for path %s "
		    "(interface=%s, member=%s, sender=%s)",
		    path, dbus_message_get_interface(msg), member,
		    dbus_message_get_sender(msg));
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	network = g_hash_table_lookup(task->bss, path);
	if (network == NULL) {
		/* signal for unknown BSS */
		_DBG_WIFI("%s not in BSS table", path);
		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
	}

	if (g_str_equal(member, "PropertiesChanged") == TRUE)
		bss_signal_properties_changed(task, network, msg);
	else
		_DBG_WIFI("BSS.%s not handled", member);

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

/*
 * Filter signals from the supplicant.
 */
static DBusHandlerResult supplicant_filter(DBusConnection *conn,
						DBusMessage *msg, void *data)
{
	if (dbus_message_has_interface(msg, SUPPLICANT_INTERFACE_INTF) == TRUE)
		return interface_filter(msg, data);
	if (dbus_message_has_interface(msg, SUPPLICANT_BSS_INTF) == TRUE)
		return bss_filter(msg, data);

	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

/*
 * Connman device driver support.
 */

/*
 * BSS g_hash_table callback on free; remove the network from the
 * device and drop our ref on the object.
 */
static void bss_drop_ref(gpointer data)
{
	struct connman_network *network = (struct connman_network *) data;
	struct connman_device *device = connman_network_get_device(network);

	_DBG_WIFI("network %p identifier %s", network,
	    connman_network_get_identifier(network));

	connman_device_remove_network(device,
	    connman_network_get_identifier(network));
	connman_network_unref(network);
}

static int supplicant_device_probe(struct connman_device *device)
{
	return 0;
}

static int supplicant_device_enable(struct connman_device *device)
{
	struct supplicant_task *task;
	int err;

	_DBG_WIFI("device %p", device);

	task = g_try_new0(struct supplicant_task, 1);
	if (task == NULL)
		return -ENOMEM;

	task->netblocks = g_hash_table_new_full(g_str_hash, g_str_equal,
	    g_free, g_free);
	if (task->netblocks == NULL) {
		err = -ENOMEM;
		goto failed;
	}

	task->bss = g_hash_table_new_full(g_str_hash, g_str_equal,
	    g_free, bss_drop_ref);
	if (task->bss == NULL) {
		err = -ENOMEM;
		goto failed;
	}

	task->ifindex = connman_device_get_index(device);
	task->ifname = connman_inet_ifname(task->ifindex);
	if (task->ifname == NULL) {
		err = -ENOMEM;
		goto failed;
	}

	task->device = connman_device_ref(device);

	task->scanning = FALSE;
	task->state = WPA_INACTIVE;
	/* NB: ptr's NULL by way of g_try_new0 */

	err = interface_create(task);
failed:
	if (err < 0 && err != -EINPROGRESS)
		free_task(task);
	return err;
}

static int supplicant_device_disable(struct connman_device *device)
{
	int index = connman_device_get_index(device);
	struct supplicant_task *task;

	_DBG_WIFI("device %p", device);

	task = find_task_by_index(index);
	if (task == NULL)
		return -ENODEV;
	/*
	 * NB: remove the task so nothing happens asynchronously
	 * (e.g. via D-Bus msgs) while we tear down the state.
	 */
	task_list = g_slist_remove(task_list, task);
	if (task->scan_call != NULL) {
		dbus_pending_call_cancel(task->scan_call);
		task->scan_call = NULL;
	}

	connman_device_set_scanning_state(task->device, FALSE);

	/* TODO(sleffler) reclaim local state on failure */
	return interface_remove(task);
}

static int supplicant_device_scan(struct connman_device *device)
{
	int index = connman_device_get_index(device);
	struct supplicant_task *task;
	int err;

	_DBG_WIFI("device %p", device);

	task = find_task_by_index(index);
	if (task == NULL)
		return -ENODEV;

	switch (task->state) {
	case WPA_SCANNING:
		return -EALREADY;
	case WPA_ASSOCIATING:
	case WPA_ASSOCIATED:
	case WPA_4WAY_HANDSHAKE:
	case WPA_GROUP_HANDSHAKE:
		return -EBUSY;
	default:
		break;
	}

	err = interface_scan(task);
	return (err == -EINPROGRESS ? 0 : err);
}

static struct connman_device_driver supplicant_device_driver = {
	.name		= "wifi",
	.type		= CONNMAN_DEVICE_TYPE_WIFI,
	.probe		= supplicant_device_probe,
	.enable		= supplicant_device_enable,
	.disable	= supplicant_device_disable,
	.scan		= supplicant_device_scan,
};

/*
 * Connman network driver support.
 */

static int supplicant_network_probe(struct connman_network *network)
{
	return 0;
}

static void supplicant_network_remove(struct connman_network *network)
{
}

static int task_connect(struct supplicant_task *task,
	struct connman_network *network)
{
	const char *security, *service_path;
	const void *ssid;
	unsigned int ssid_len;
	gpointer handle;
	int err;

	security = connman_network_get_string(network, "WiFi.Security");
	ssid = connman_network_get_blob(network, "WiFi.SSID", &ssid_len);

	task_info(task, "connect SSID \"%.*s\" BSSID %s security %s",
	    ssid_len, ssid, connman_network_get_string(network, "Address"),
	    security);

	/*
	 * Validate security credentials here; doing it later when
	 * constructing netblock contents results in side effects
	 * or requires complicated back out of state.
	 */
	err = 0;
	if (security == NULL)
		err = -EINVAL;
	else if (g_ascii_strcasecmp(security, "802_1x") == 0)
		err = check_8021x(network);
        else if (g_ascii_strcasecmp(security, "psk") == 0 ||
	    g_ascii_strcasecmp(security, "wpa") == 0 ||
	    g_ascii_strcasecmp(security, "rsn") == 0)
		err = check_psk(network);
	else if (is_dynamic_wep(security, network) == TRUE)
		err = check_8021x(network);
	else if (g_ascii_strcasecmp(security, "wep") == 0)
		err = check_wep(network);
	if (err != 0) {
		_DBG_WIFI("security failed, network %p security %s err %d",
		    network, security, err);
		return err;
	}

	handle = network_get_handle(task, network, &service_path);
	if (handle != NULL) {
		if (service_path == NULL) {
			_DBG_WIFI("no service for network %p", network);
			return -EINVAL;
		}

		/* XXX hack to work around network_set_properties not working */
		network_remove(task, handle);
		g_hash_table_remove(task->netblocks, service_path);
		handle = NULL;
	}
	if (handle == NULL) {
		/*
		 * No supplicant network block; setup one.
		 */
		handle = network_add(task, network);
		if (handle == NULL) {
			_DBG_WIFI("no handle for network %p service path %s",
			    network, service_path);
			return -EIO;
		}

		_DBG_WIFI("add service %s handle %s", service_path,
		    (const char *) handle);
		/* NB: copy service_path as it may get free'd */
		g_hash_table_insert(task->netblocks,
		    (gpointer) g_strdup(service_path), handle);
	} else {
		/*
		 * Re-write the netblock properties.
		 * TODO(sleffler) maybe optimize update
		 */
		network_set_properties(task, network, handle);
	}

	/* select network block; the supplicant will automatically connect */
	err = network_select(task, handle);
	if (err < 0) {
		_DBG_WIFI("select failed, err %d", err);
		return err;
	}

	/* committed, hold a reference to the network */
	task->network = connman_network_ref(network);
	_DBG_WIFI("network %p", task->network);
	return -EINPROGRESS;
}

static int supplicant_network_connect(struct connman_network *network)
{
	struct supplicant_task *task;
	int index = connman_network_get_index(network);

	task = find_task_by_index(index);
	if (task == NULL) {
		_DBG_WIFI("network %p no task", network);
		return -ENODEV;
	}

	if (task->network != NULL) {
		if (task->network == network)
			return -EINPROGRESS;
		task_error(task, "%s: request %p but %p already connecting",
		    __func__, network, task->network);
		return -EINVAL;
	}

	connman_network_set_connecting(network);

	return task_connect(task, network);
}

static int supplicant_network_disconnect(struct connman_network *network)
{
	struct supplicant_task *task;
	int index = connman_network_get_index(network);
	void *handle;

	task = find_task_by_index(index);
	if (task == NULL)
		return -ENODEV;

	_DBG_WIFI("network %p current (bss %p network %p)", network,
	    task->current_bss, task->network);

	if (task->current_bss != network && task->network != network) {
		/*
		 * Should not happen.  We were asked to disconnect a network
		 * that is not currently active.  Just return 0 so the core
		 * will mark it disconnected.  This indicates a problem in
		 * the upper layers.
		 */
		task_error(task, "%s: network %p not active "
		    "(bss %p network %p)", __func__, network,
		    task->current_bss, task->network);
		return 0;
	}

	connman_network_set_disconnecting(network);

	if (task->network == network) {
		/* NB: immediately clear state since we may not be notified */
		connman_network_unref(task->network);
		task->network = NULL;
	}

	if (interface_disconnect(task) < 0) {
		/*
		 * Disable any netblock so we don't continue scanning.
		 */
		handle = network_get_handle(task, network, NULL);
		if (handle != NULL)
			network_disable(task, handle);
		/*
		 * Clear our state in case of an error.  Should not happen.
		 * TODO(sleffler) add diagnostic
		 */
		if (task->current_bss == network)
			unref_current_bss(task);
	}
	/* NB: caller marks network disconnected on 0 return */
	return 0;
}

static struct connman_network_driver supplicant_network_driver = {
	.name		= "wifi",
	.type		= CONNMAN_NETWORK_TYPE_WIFI,
	.probe		= supplicant_network_probe,
	.remove		= supplicant_network_remove,
	.connect	= supplicant_network_connect,
	.disconnect	= supplicant_network_disconnect,
};

/*
 * Resume notifier callback.
 */
static void supplicant_system_resume_handler(void)
{
	time_t now = time(0);
	GSList *list;

	_DBG_WIFI("system resuming");

	/*
	 * Handle resume.  There are two tasks we need to perform:
	 * re-association to an AP (or roam to a new AP), and flush
	 * stale entries from supplicant's BSS cache.
	 *
	 * For re-association/roaming there are three cases:
	 * 1) we resume in range of the same AP and fast enough that
	 *    the AP has not dropped us due to inactivity
	 * 2) we resume in range of the same AP but slow enough that
	 *    the AP has dropped us due to inactivity
	 * 3) we resume out of range of the AP
	 *
	 * For 1) the kernel will probe the AP and user space knows
	 * nothing.  For 2) supplicant notifies us the AP deauth/disassoc'd
	 * us and it will immediately scan.  For 3) supplicant notifies
	 * us the connection was lost and will immediately scan.
	 *
	 * For 2) and 3) we use the BSS change signal to trigger autoconnect
	 * and either re-join the same AP or roam to a new AP.
	 *
	 * BSS cache flushing requires more care.  We don't immediately
	 * flush the cache because this may conflict with a scan and/or
	 * connect request (for the latter we race against connect and
	 * may cause the request to be aborted).  Instead we mark each
	 * task so a flush is done at the completion of the next scan.
	 * In cases 2) and 3) the flush will happen on scan complete.
	 * In case 1) the flush will happen at some later time but because
	 * we flush cache entries using an age that is calculated based
	 * on the resume time this is ok (typically it is a noop).
	 */
	for (list = task_list; list; list = list->next) {
		struct supplicant_task *task = list->data;
		/*
		 * Schedule the flush on completion of the next scan.
		 */
		task->resume_time = now;
		task->flush_pending = TRUE;

		/*
		 * If we are not connected or trying to connect, trigger
		 * a scan since we may now be in range of a network we can
		 * autoconnect to but nothing will happen w/o scan results.
		 */
		if (task->current_bss == NULL && task->network == NULL)
			(void) interface_scan(task);
	}
}

/*
 * Country code changed notifier callback.
 */
static void supplicant_country_changed_handler(const char *country)
{
	GSList *list;

	_DBG_WIFI("country %s", country);

	for (list = task_list; list; list = list->next) {
		struct supplicant_task *task = list->data;

		interface_set_country(task, country);
	}
}

/*
 * Profile pop notifier callback
 *
 * When a profile is removed, we want wpa_supplicant to remove any
 * any cache credentials which it derived from previous connections.
 */
static void supplicant_profile_pop_handler(struct connman_profile *profile)
{
	GSList *list;

	_DBG_WIFI("profile %p", profile);

	for (list = task_list; list; list = list->next) {
		struct supplicant_task *task = list->data;

		interface_clear_cached_credentials(task);
	}
}

static struct connman_notifier wifi_notifier = {
        .name			= "wifi",
        .priority		= CONNMAN_NOTIFIER_PRIORITY_DEFAULT,
        .system_resume		= supplicant_system_resume_handler,
	.country_changed	= supplicant_country_changed_handler,
	.profile_pop		= supplicant_profile_pop_handler,
};

/*
 * D-bus service watch callbacks.
 */

static void supplicant_probe(DBusConnection *conn, void *user_data)
{
	_DBG_WIFI("conn %p", conn);

	if (connman_device_driver_register(&supplicant_device_driver) < 0)
		connman_error("Failed to register WiFi driver");
}

static void supplicant_remove(DBusConnection *conn, void *user_data)
{
	_DBG_WIFI("conn %p", conn);

	connman_device_driver_unregister(&supplicant_device_driver);
}

static const char *interface_rule = "type=signal,"
				"interface=" SUPPLICANT_INTERFACE_INTF;
static const char *bss_rule = "type=signal,"
				"interface=" SUPPLICANT_BSS_INTF;
static guint watch;

static void new_wifi_finis(void);

static int new_wifi_init(void)
{
	DBusMessage *message;
	int err;

	connection = connman_dbus_get_connection();
	if (connection == NULL) {
		connman_error("%s: cannot get dbus connection", __func__);
		return -EIO;
	}

	_DBG_WIFI("connection %p", connection);

	if (dbus_connection_add_filter(connection,
				supplicant_filter, NULL, NULL) == FALSE) {
		connman_error("%s: cannot add dbus filter", __func__);
		dbus_connection_unref(connection);
		connection = NULL;
		return -EIO;
	}

	dbus_bus_add_match(connection, interface_rule, NULL);
	dbus_bus_add_match(connection, bss_rule, NULL);
	dbus_connection_flush(connection);

	watch = g_dbus_add_service_watch(connection, SUPPLICANT_NAME,
			supplicant_probe, supplicant_remove, NULL, NULL);

	message = dbus_message_new_method_call(SUPPLICANT_NAME, "/",
				DBUS_INTERFACE_INTROSPECTABLE, "Introspect");
	if (message != NULL) {
		dbus_message_set_no_reply(message, TRUE);
		dbus_connection_send(connection, message, NULL);
		dbus_message_unref(message);
	}

	err = connman_network_driver_register(&supplicant_network_driver);
	if (err < 0) {
		connman_error("%s: cannot register network driver (err %d)",
		    __func__, err);
		new_wifi_finis();
		return err;
	}

	err = connman_notifier_register(&wifi_notifier);
	if (err < 0) {
		connman_error("%s: cannot register wifi notifier (err %d)",
		    __func__, err);
		new_wifi_finis();
		return err;
	}

	return 0;
}

static void new_wifi_finis(void)
{
	_DBG_WIFI("connection %p", connection);

	connman_device_driver_unregister(&supplicant_device_driver);
	connman_notifier_unregister(&wifi_notifier);
	connman_network_driver_unregister(&supplicant_network_driver);

	if (watch > 0)
		g_dbus_remove_watch(connection, watch);

	dbus_bus_remove_match(connection, bss_rule, NULL);
	dbus_bus_remove_match(connection, interface_rule, NULL);
	dbus_connection_flush(connection);
	dbus_connection_remove_filter(connection, supplicant_filter, NULL);
	dbus_connection_unref(connection);
	connection = NULL;
}

CONNMAN_PLUGIN_DEFINE(newwifi, "New WiFi interface", VERSION,
    CONNMAN_PLUGIN_PRIORITY_DEFAULT, new_wifi_init, new_wifi_finis)
